1 module hip.data.json; 2 import hip.util.array; 3 4 5 JSONValue parseJSON(string jsonData) 6 { 7 JSONValue ret = JSONValue.parse(jsonData); 8 if(ret.hasErrorOccurred) 9 throw new Exception(ret.error); 10 return ret; 11 } 12 13 T tryGetValue(T)(JSONValue v, string prop, T defaultValue = T.init) 14 { 15 JSONValue* ret = prop in v; 16 if(ret) 17 { 18 static if(is(T == float)) return ret.floating; 19 else static if(is(T == int)) return ret.integer; 20 else return ret.get!T; 21 } 22 return defaultValue; 23 } 24 25 alias JSONObject = JSONValue[string]; 26 27 struct JSONArray 28 { 29 size_t length() const { return value.length; } 30 private CacheArray!(JSONValue, 4) value; 31 32 /** 33 * Small array that holds up to N members in static memory. Whenever bigger than N, 34 * uses default D dynamic array. 35 */ 36 private static struct CacheArray(T, size_t N) 37 { 38 private T[N] staticData; 39 private T[] dynData; 40 private size_t actualLength; 41 42 this(T[] value) 43 { 44 this.set(value); 45 } 46 47 private void set(T[] values) 48 { 49 if(values.length <= N) 50 staticData.ptr[0..values.length] = values[]; 51 else 52 { 53 if(dynData is null) 54 dynData = values.dup; 55 else 56 { 57 if(dynData.length < values.length) 58 dynData.length = values.length; 59 dynData[0..values.length] = values[]; 60 } 61 } 62 actualLength = values.length; 63 } 64 private void append(T value) 65 { 66 T[] temp = (&value)[0..1]; 67 append(temp); 68 } 69 70 private void append(T[] values) 71 { 72 import core.stdc.string; 73 if(actualLength + values.length <= N) 74 { 75 memcpy(staticData.ptr + actualLength, values.ptr, values.length * T.sizeof); 76 } 77 else 78 { 79 if(dynData is null) 80 { 81 dynData = uninitializedArray!(T[])(actualLength+values.length); 82 memcpy(dynData.ptr, staticData.ptr, actualLength * T.sizeof); 83 } 84 else if (dynData.length < actualLength + values.length) 85 { 86 size_t newSize = actualLength + values.length; 87 88 version(WebAssembly) 89 { 90 //TODO: Needs to fix somewhere in the walloc allocator. Realloc is buggy currently 91 T[] newDyn = new T[newSize]; 92 newDyn[0..actualLength] = dynData[0..actualLength]; 93 dynData = newDyn; 94 } 95 else 96 dynData.length = newSize; 97 } 98 memcpy(dynData.ptr + actualLength, values.ptr, values.length * T.sizeof); 99 } 100 actualLength+= values.length; 101 } 102 103 void trim() 104 { 105 if(dynData !is null) 106 dynData.length = actualLength; 107 } 108 size_t length() const { return actualLength; } 109 inout(T)[] getArray() inout 110 { 111 if(dynData !is null) 112 return dynData[0..actualLength]; 113 return staticData[0..actualLength]; 114 } 115 } 116 117 this(JSONValue[] v) 118 { 119 this.value = CacheArray!(JSONValue, 4)(v); 120 } 121 122 static JSONArray* append(JSONArray* self, JSONValue v) 123 { 124 self.value.append(v); 125 return self; 126 } 127 auto opOpAssign(string op, T)(T value) if(op == "~") 128 { 129 static if(is(T == JSONValue)) 130 append(&this, value); 131 else 132 append(&this, JSONValue(value)); 133 return this; 134 } 135 private static JSONArray* trim(JSONArray* self) 136 { 137 self.value.trim(); 138 return self; 139 } 140 141 static JSONArray* createNew() 142 { 143 return new JSONArray([]); 144 } 145 146 static JSONArray* createNew(JSONValue[] data) 147 { 148 JSONArray* ret = new JSONArray(data); 149 return ret; 150 } 151 152 JSONValue[] getArray(){return value.getArray;} 153 const(JSONValue)[] getArray() const {return value.getArray;} 154 } 155 156 private enum JSONState 157 { 158 key, 159 lookingAssignment, 160 lookingForNext, 161 value 162 } 163 164 enum JSONType : ubyte 165 { 166 bool_ = 0, 167 float_ = 1, 168 int_ = 2, integer = int_, uinteger = int_, 169 string_ = 3, string = string_, 170 array = 4, 171 object = 5, 172 error = 6, 173 null_ = 7 //0b111 174 } 175 176 pragma(inline, true) 177 bool isWhitespace(char ch) 178 { 179 switch(ch) 180 { 181 case ' ', '\t', '\n', '\r': return true; 182 default: return false; 183 } 184 } 185 186 pragma(inline, true) bool isNumber(char ch){return '0' <= ch && ch <= '9';} 187 pragma(inline, true) bool isNumeric(char ch){return ('0' <= ch && ch <= '9') || ch == '-' || ch == '.';} 188 189 private union JSONData 190 { 191 double _float; 192 long _int; 193 bool _bool; 194 immutable(char)* _string; 195 JSONObject object; 196 JSONArray* array; 197 } 198 199 200 struct JSONValue 201 { 202 JSONData data; 203 static if(size_t.sizeof == uint.sizeof) 204 { 205 ///Used only for the string. 206 uint _length; 207 208 pragma(inline, true) JSONType type(JSONType t) 209 { 210 _length = (_length & 0x1FFFFFFF) | (cast(uint)t << 29); 211 return t; 212 } 213 pragma(inline, true) JSONType type() const 214 { 215 return cast(JSONType)(_length >> 29); 216 } 217 218 pragma(inline, true) private void setString(string s) 219 { 220 import core.stdc.string; 221 data._string = s.ptr; 222 _length = (s.length & 0x1FFFFFFF) | (cast(uint)type << 29); 223 } 224 225 pragma(inline, true) private uint length() const 226 { 227 return _length & 0x1FFFFFFF; 228 } 229 } 230 else 231 { 232 ///Used only for the string. 233 ulong _length; 234 235 pragma(inline, true) JSONType type(JSONType t) 236 { 237 _length = (_length & 0x1FFFFFFFFFFFFFFF) | (cast(ulong)t << 61); 238 return t; 239 } 240 pragma(inline, true) JSONType type() const 241 { 242 return cast(JSONType)(_length >> 61); 243 } 244 245 pragma(inline, true) private void setString(string s) 246 { 247 import core.stdc.string; 248 data._string = s.ptr; 249 _length = (s.length & 0x1FFFFFFFFFFFFFFF) | (cast(ulong)type << 61); 250 } 251 252 pragma(inline, true) private ulong length() const 253 { 254 return _length & 0x1FFFFFFFFFFFFFFF; 255 } 256 } 257 258 259 this(T)(T value) 260 { 261 import std.traits; 262 static if(isIntegral!T) 263 { 264 type = JSONType.int_; 265 data._int = value; 266 } 267 else static if(isFloatingPoint!T) 268 { 269 type = JSONType.float_; 270 data._float = value; 271 } 272 else static if(is(T == bool)) 273 { 274 type = JSONType.bool_; 275 data._bool = value; 276 } 277 else static if(is(T == string)) 278 { 279 type = JSONType.string_; 280 setString(value); 281 } 282 else static if(is(T == JSONObject)) 283 { 284 type = JSONType.object; 285 data.object = value; 286 } 287 else static if(is(T == JSONArray*)) 288 { 289 type = JSONType.array; 290 data.array = value; 291 } 292 else static if(is(T == JSONValue[])) 293 { 294 data.array = JSONArray.createNew(value); 295 type = JSONType.array; 296 } 297 else static if(is(T == JSONValue)) 298 { 299 type = value.type; 300 data = value.data; 301 } 302 else static if(is(T == typeof(null))) 303 { 304 data.object = null; 305 type = JSONType.null_; 306 } 307 else static assert(false, "Unsupported type ", T.stringof); 308 } 309 pragma(inline, false) 310 { 311 int integer() const 312 { 313 return type == JSONType.integer ? get!int : cast(int)get!float; 314 } 315 float floating() const 316 { 317 return type == JSONType.float_ ? get!float : cast(float)get!int; 318 } 319 } 320 321 pragma(inline, true) 322 { 323 bool boolean() const {return get!bool;} 324 string str() const {return get!string;} 325 string error() const{return get!string;} 326 } 327 328 ///Returns an array range. 329 auto array() const 330 { 331 import hip.util.exception; 332 enforce(type == JSONType.array, "Tried to iterate a non array object of type "~getTypeName); 333 struct JSONValueArrayIterator 334 { 335 private const(JSONArray*) arr; 336 private size_t idx = 0; 337 size_t length(){return arr.length;} 338 bool empty(){return idx == arr.length;} 339 void popFront(){idx++;} 340 const(JSONValue) front(){return arr.getArray()[idx];} 341 const(JSONValue) opIndex(size_t num){return arr.getArray()[num];} 342 } 343 return JSONValueArrayIterator(data.array); 344 } 345 346 ref JSONArray jsonArray() 347 { 348 return *data.array; 349 } 350 351 JSONValue[] array() 352 { 353 import hip.util.exception; 354 enforce(type == JSONType.array, "Tried to iterate a non array object of type "~getTypeName); 355 return data.array.getArray(); 356 } 357 358 JSONValue object() const 359 { 360 import hip.util.exception; 361 enforce(type == JSONType.object, "Tried to get type object but value is of type "~getTypeName); 362 JSONValue ret; 363 ret.data = data; 364 ret.type = JSONType.object; 365 return ret; 366 } 367 368 string[] keys() 369 { 370 import hip.util.exception; 371 enforce(type == JSONType.object, "Tried to get type object but value is of type "~getTypeName); 372 return data.object.keys; 373 } 374 JSONValue[] values() 375 { 376 import hip.util.exception; 377 enforce(type == JSONType.object, "Tried to get type object but value is of type "~getTypeName); 378 return data.object.values; 379 } 380 381 string getTypeName() const 382 { 383 final switch(type) with(JSONType) 384 { 385 case int_: return "int"; 386 case bool_: return "bool"; 387 case float_: return "float"; 388 case string_: return "string"; 389 case object: return "object"; 390 case array: return "array"; 391 case error: return "error"; 392 case null_: return "null"; 393 } 394 } 395 396 T get(T)() const 397 { 398 import std.traits; 399 import hip.util.exception; 400 static if(isIntegral!T) 401 { 402 enforce(type == JSONType.int_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 403 return cast(Unqual!T)data._int; 404 } 405 else static if(isFloatingPoint!T) 406 { 407 enforce(type == JSONType.float_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 408 return cast(Unqual!T)data._float; 409 } 410 else static if(is(T == bool)) 411 { 412 enforce(type == JSONType.bool_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 413 return cast(Unqual!T)data._bool; 414 } 415 else static if(is(T == string)) 416 { 417 enforce(type == JSONType.string_ || type == JSONType.error, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 418 return data._string[0..cast(size_t)length]; 419 } 420 else static if(is(T == JSONObject)) 421 { 422 enforce(type == JSONType.object, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 423 return data.object; 424 } 425 else static if(is(T == JSONArray)) 426 { 427 enforce(type == JSONType.array, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 428 return *data.array; 429 } 430 } 431 bool isNull() 432 { 433 with(JSONType) 434 { 435 if(type == null_) return true; 436 if(type == JSONType.object) return data.object == null; 437 if(type == JSONType.array) return data.array == null; 438 } 439 return false; 440 } 441 442 static JSONValue emptyObject() 443 { 444 JSONValue ret; 445 ret.type = JSONType.object; 446 ret.data.object = new JSONObject(); 447 return ret; 448 } 449 static JSONValue emptyArray() 450 { 451 JSONValue ret; 452 ret.type = JSONType.array; 453 ret.data.array = JSONArray.createNew(); 454 return ret; 455 } 456 457 private static JSONValue create(T)(T data) 458 { 459 JSONValue ret = JSONValue(data); 460 return ret; 461 } 462 private static JSONValue errorObj(string message) 463 { 464 JSONValue ret; 465 ret.setString(message); 466 ret.type = JSONType.error; 467 return ret; 468 } 469 470 471 private static JSONValue parse(string data) 472 { 473 import core.memory; 474 import hip.util.conv:to; 475 476 if(!data.length) 477 { 478 return JSONValue.errorObj("No data provided"); 479 } 480 ptrdiff_t index = 0; 481 StringPool pool = StringPool(cast(size_t)(data.length*0.75)); 482 483 bool getNextString(string data, ptrdiff_t currentIndex, out ptrdiff_t newIndex, out string theString) 484 { 485 assert(data[currentIndex] == '"', "getNextString must start with a quotation mark"); 486 ptrdiff_t i = currentIndex + 1; 487 size_t returnLength = 0; 488 char[] ret = pool.getNewString(64); 489 char ch; 490 491 while(i < data.length) 492 { 493 ch = data[i]; 494 switch(ch) 495 { 496 case '"': 497 ret = pool.resizeString(ret, returnLength); 498 newIndex = i; 499 theString = cast(string)ret; 500 return true; 501 case '\\': 502 if(i + 1 >= data.length) 503 return false; 504 ch = escapedCharacter(data[++i]); 505 break; 506 default: break; 507 508 } 509 if(returnLength >= ret.length) 510 ret = pool.resizeString(ret, ret.length*2); 511 512 ret[returnLength++] = ch; 513 i++; 514 } 515 return false; 516 } 517 518 519 bool getNextNumber(string data, ptrdiff_t currentIndex, out ptrdiff_t newIndex, out JSONData theData, out JSONType type) 520 { 521 assert(data[currentIndex].isNumeric); 522 bool hasDecimal = false; 523 newIndex = currentIndex; 524 if(data[currentIndex] == '-') 525 newIndex++; 526 527 while(newIndex < data.length) 528 { 529 if(!hasDecimal && data[newIndex] == '.') 530 { 531 hasDecimal = true; 532 if(newIndex+1 < data.length) newIndex++; 533 } 534 if(!isNumber(data[newIndex])) 535 break; 536 newIndex++; 537 } 538 if(hasDecimal) 539 { 540 theData._float = to!double(data[currentIndex..newIndex]); 541 type = JSONType.float_; 542 } 543 else 544 { 545 theData._int = to!long(data[currentIndex..newIndex]); 546 type = JSONType.int_; 547 } 548 //Stopped on a non number. Revert 1 step. 549 newIndex--; 550 return newIndex < data.length; 551 } 552 JSONValue ret; 553 ret.type = JSONType.null_; 554 JSONValue* current = &ret; 555 JSONState state = JSONState.value; 556 JSONValue lastValue = ret; 557 558 scope JSONValue[] stack = uninitializedArray!(JSONValue[])(32); 559 scope ptrdiff_t stackLength = 0; 560 561 size_t line = 0; 562 string getErr(string err="", string f = __FILE_FULL_PATH__, size_t l = __LINE__) 563 { 564 return "Error at line "~line.to!string~" "~err~" on index '"~index.to!string~"' last parsed: "~lastValue.toString~" [Internal: "~f~":"~l.to!string~"]"; 565 } 566 567 string lastKey; 568 do 569 { 570 char ch = data[index]; 571 switch(ch) 572 { 573 case '\n': 574 line++; 575 break; 576 case '{': 577 { 578 if(state != JSONState.value) 579 return JSONValue.errorObj(getErr()); 580 JSONValue obj = JSONValue.create(new JSONObject); 581 if(!pushNewScope(obj, current, stackLength, stack, lastKey)) 582 return JSONValue.errorObj(getErr("Could not push new scope in JSON. Only array, object or null are valid")); 583 584 state = JSONState.key; 585 break; 586 } 587 case '}': 588 popScope(stackLength, stack, current); 589 state = JSONState.lookingForNext; 590 break; 591 case ':': 592 if(state != JSONState.lookingAssignment) 593 return JSONValue.errorObj(getErr("expected key before ':'")); 594 state = JSONState.value; 595 break; 596 case '"': 597 { 598 599 switch(state) 600 { 601 case JSONState.lookingForNext: 602 if(current.type == JSONType.object) 603 goto case JSONState.key; 604 else if(current.type == JSONType.array) 605 goto case JSONState.value; 606 goto default; 607 case JSONState.key: 608 { 609 assert(current.type == JSONType.object, getErr("only object can receive a key.")); 610 if(!getNextString(data, index, index, lastKey)) 611 return JSONValue.errorObj(getErr("unclosed quotes.")); 612 state = JSONState.lookingAssignment; 613 break; 614 } 615 case JSONState.value: 616 { 617 string val; 618 if(!getNextString(data, index, index, val)) 619 return JSONValue.errorObj(getErr("unclosed quotes.")); 620 pushToStack(JSONValue(val), current, lastValue, lastKey); 621 state = JSONState.lookingForNext; 622 break; 623 } 624 default: 625 return JSONValue.errorObj(getErr("comma expected before key "~lastKey)); 626 } 627 break; 628 } 629 case '[': 630 { 631 if(state != JSONState.lookingForNext && state != JSONState.value) 632 return JSONValue.errorObj(getErr(" expected to be a value. ")); 633 if(!pushNewScope(JSONValue(JSONArray.createNew()), current, stackLength, stack, lastKey)) 634 return JSONValue.errorObj(getErr("Could not push new scope in JSON. Only array, object or null are valid")); 635 state = JSONState.value; 636 break; 637 } 638 case ']': 639 if(state != JSONState.lookingForNext && state != JSONState.value) 640 return JSONValue.errorObj(getErr("expected to be a value. ")); 641 popScope(stackLength, stack, current); 642 state = JSONState.lookingForNext; 643 break; 644 case ',': 645 if(state != JSONState.lookingForNext) 646 return JSONValue.errorObj(getErr("unexpected comma. ")); 647 if(current.type != JSONType.object && current.type != JSONType.array) 648 return JSONValue.errorObj(getErr("unexpected comma. ")); 649 650 switch(current.type) with(JSONType) 651 { 652 case object: state = JSONState.key; break; 653 case array: state = JSONState.value; break; 654 default: assert(false, "Error?"); 655 } 656 break; 657 default: 658 switch(state) 659 { 660 case JSONState.value: //Any value 661 case JSONState.lookingForNext: //Array 662 if(ch.isNumeric) 663 { 664 if(state == JSONState.lookingForNext && current.type != JSONType.array) 665 return JSONValue.errorObj(getErr("unexpected number.")); 666 JSONType out_type; 667 if(!getNextNumber(data, index, index, lastValue.data, out_type)) 668 return JSONValue.errorObj(getErr("unexpected end of file.")); 669 lastValue.type = out_type; 670 pushToStack(lastValue, current, lastValue, lastKey); 671 state = JSONState.lookingForNext; 672 } 673 else if(index + "true".length < data.length && data[index.."true".length + index] == "true") 674 { 675 if(state == JSONState.lookingForNext && current.type != JSONType.array) 676 return JSONValue.errorObj(getErr("unexpected number.")); 677 pushToStack(JSONValue(true), current, lastValue, lastKey); 678 state = JSONState.lookingForNext; 679 } 680 else if(index + "false".length < data.length && data[index.."false".length + index] == "false") 681 { 682 if(state == JSONState.lookingForNext && current.type != JSONType.array) 683 return JSONValue.errorObj(getErr("unexpected number.")); 684 pushToStack(JSONValue(false), current, lastValue, lastKey); 685 state = JSONState.lookingForNext; 686 } 687 else if(index + "null".length < data.length && data[index.."null".length + index] == "null") 688 { 689 if(state == JSONState.lookingForNext && current.type != JSONType.array) 690 return JSONValue.errorObj(getErr("unexpected number.")); 691 pushToStack(JSONValue(null), current, lastValue, lastKey); 692 state = JSONState.lookingForNext; 693 } 694 break; 695 default:break; 696 } 697 break; 698 } 699 index++; 700 } 701 while(index < data.length && stack.length > 0); 702 return ret; 703 } 704 705 const(JSONValue) opIndex(string key) const 706 { 707 assert(type == JSONType.object, "Can't get a member from a non object."); 708 return data.object[key]; 709 } 710 JSONValue opIndex(string key) 711 { 712 assert(type == JSONType.object, "Can't get a member from a non object."); 713 return data.object[key]; 714 } 715 JSONValue opIndexAssign(JSONValue v, string key) 716 { 717 import hip.util.exception; 718 enforce(type == JSONType.object, "Can't get a member from a non object."); 719 enforce(data.object !is null, "Can't access a null object"); 720 data.object[key] = v; 721 return data.object[key]; 722 } 723 724 JSONValue opIndexAssign(T)(T value, string key) if(!is(T == JSONValue)) 725 { 726 return opIndexAssign(JSONValue(value), key); 727 } 728 729 const(JSONValue)* opBinaryRight(string op)(string key) const 730 if(op == "in") 731 { 732 if(type != JSONType.object) return null; 733 return key in data.object; 734 } 735 JSONValue* opBinaryRight(string op)(string key) 736 if(op == "in") 737 { 738 if(type != JSONType.object) return null; 739 return key in data.object; 740 } 741 742 int opApply(scope int delegate(string key, JSONValue v) dg) 743 { 744 if(type != JSONType.object) 745 { 746 assert(false, "Can't iterate with key[string] and value[JSONValue] an object of type "~getTypeName); 747 } 748 int result = 0; 749 foreach (k, v ; data.object) 750 { 751 result = dg(k, v); 752 if (result) 753 break; 754 } 755 756 return result; 757 } 758 bool hasErrorOccurred() const { return type == JSONType.error; } 759 760 /** 761 * 762 * Params: 763 * compressed = Won't include any space in the file. Also, won't escape backslash. Since the parsing works with a single backslash. 764 * this may reduce the json size, and increase the parsing speed. 765 * selfPrintkey = Only used for objects. 766 * Returns: 767 */ 768 string toString(bool compressed = false)() const 769 { 770 if(type == JSONType.error) 771 return error(); 772 import hip.util.conv:to; 773 string ret; 774 775 static string escapeCharacters(string input) 776 { 777 size_t length = input.length; 778 foreach(ch; input) 779 { 780 if(ch == '\n' || ch == '\t' || ch == '\r' || ch == '"' || ch == '\\') length++; 781 } 782 if(length == input.length) return input; 783 char[] escaped = new char[](length); 784 length = 0; 785 foreach(i; 0..input.length) 786 { 787 switch(input[i]) 788 { 789 case '"': 790 escaped[length] = '\\'; 791 escaped[++length] = '"'; 792 break; 793 case '\\': 794 escaped[length] = '\\'; 795 escaped[++length] = '\\'; 796 break; 797 case '\n': 798 escaped[length] = '\\'; 799 escaped[++length] = 'n'; 800 break; 801 case '\r': 802 escaped[length] = '\\'; 803 escaped[++length] = 'r'; 804 break; 805 case '\t': 806 escaped[length] = '\\'; 807 escaped[++length] = 't'; 808 break; 809 default: 810 escaped[length] = input[i]; 811 break; 812 } 813 length++; 814 } 815 return cast(string)escaped; 816 } 817 818 final switch ( type ) 819 { 820 case JSONType.int_: 821 ret = data._int.to!(string); 822 break; 823 case JSONType.float_: 824 ret = data._float.to!string; 825 break; 826 case JSONType.bool_: 827 ret = data._bool ? "true" : "false"; 828 break; 829 case JSONType.error: 830 ret = error(); 831 break; 832 case JSONType.string_: 833 ret = '"'~escapeCharacters(get!string)~'"'; 834 break; 835 case JSONType.null_: 836 ret = "null"; 837 break; 838 case JSONType.array: 839 { 840 ret = "["; 841 bool isFirst = true; 842 foreach(v; data.array.getArray) 843 { 844 static if(compressed) 845 { 846 if(!isFirst) 847 ret~= ','; 848 } 849 else 850 { 851 if(!isFirst) 852 ret~= ", "; 853 } 854 isFirst = false; 855 ret~= v.toString!compressed(); 856 } 857 ret~= "]"; 858 break; 859 } 860 case JSONType.object: 861 { 862 863 ret~= '{'; 864 bool isFirst = true; 865 foreach(k, v; data.object) 866 { 867 static if(compressed) 868 { 869 if(!isFirst) 870 ret~= ','; 871 } 872 873 else 874 { 875 if(!isFirst) 876 ret~= ", "; 877 } 878 isFirst = false; 879 static if(compressed) 880 ret~= '"'~escapeCharacters(k)~"\":"~v.toString!compressed; 881 else 882 ret~= '"'~escapeCharacters(k)~"\" : "~v.toString!compressed; 883 } 884 ret~= '}'; 885 break; 886 887 } 888 } 889 return ret; 890 } 891 892 void dispose() 893 { 894 if(type == JSONType.object) 895 { 896 foreach(v; data.object) 897 v.dispose(); 898 } 899 else if(type == JSONType.array) 900 { 901 foreach(v; data.array.getArray) 902 v.dispose(); 903 } 904 905 } 906 } 907 908 private struct StringPool 909 { 910 private char[] pool; 911 private size_t used; 912 913 this(size_t size) 914 { 915 this.pool = uninitializedArray!(char[])(size); 916 } 917 918 bool getSlice(size_t sliceSize, out char[] str) 919 { 920 if(used+sliceSize < pool.length) 921 { 922 str = pool[used..used+sliceSize]; 923 used+= sliceSize; 924 return true; 925 } 926 return false; 927 } 928 929 char[] resizeString(char[] str, size_t newSize) 930 { 931 ///Inside pool 932 if(newSize == str.length) return str; 933 if(pool.ptr <= str.ptr && pool.ptr + pool.length > str.ptr) 934 { 935 if(newSize > str.length) 936 { 937 if(newSize - str.length + used > pool.length) 938 { 939 used-= str.length; 940 char[] ret = uninitializedArray!(char[])(newSize); 941 ret[0..str.length] = str[]; 942 return ret; 943 } 944 else 945 { 946 ptrdiff_t offset = str.ptr - pool.ptr; 947 assert(offset >= 0, " Out of bounds?"); 948 used+= newSize - str.length; 949 return pool[cast(size_t)offset..offset+newSize]; 950 } 951 } 952 else 953 { 954 used-= str.length - newSize; 955 return str[0..newSize]; 956 } 957 } 958 else 959 str.length = newSize; 960 return str; 961 } 962 963 /** 964 * If the pool is not enough, it will allocate randomly 965 */ 966 char[] getNewString(size_t strSize) 967 { 968 char[] ret; 969 if(getSlice(strSize, ret)) 970 return ret; 971 return new char[](strSize); 972 } 973 } 974 975 pragma(inline, true) 976 bool pushNewScope(JSONValue val, ref JSONValue* current, ref ptrdiff_t stackLength, ref JSONValue[] stack, string key) 977 { 978 assert(val.type == JSONType.object || val.type == JSONType.array || val.type == JSONType.null_, "Unexpected push."); 979 JSONValue* currTemp = current; 980 981 stackLength++; 982 if(stackLength > stack.length) 983 stack~= val; 984 else 985 stack[stackLength-1] = val; 986 987 current = &stack[stackLength-1]; 988 989 990 switch(currTemp.type) 991 { 992 case JSONType.object: 993 currTemp.data.object[key] = *current; 994 break; 995 case JSONType.array: 996 currTemp.data.array = JSONArray.append(currTemp.data.array, *current); 997 break; 998 case JSONType.null_: 999 currTemp.type = val.type; 1000 currTemp.data = val.data; 1001 break; 1002 default: return false; 1003 } 1004 return true; 1005 } 1006 1007 1008 pragma(inline, true) 1009 void popScope(ref ptrdiff_t stackLength, ref JSONValue[] stack, ref JSONValue* current) 1010 { 1011 assert(stackLength > 0, "Unexpected pop."); 1012 1013 stackLength--; 1014 if(stackLength > 0) 1015 { 1016 JSONValue* next = &stack[stackLength-1]; 1017 if(current.type == JSONType.array) 1018 current.data.array = JSONArray.trim(current.data.array); 1019 current = next; 1020 import hip.util.conv; 1021 assert(current.type == JSONType.object || current.type == JSONType.array, "Unexpected value in stack. (Typed "~(cast(size_t)(current.type)).to!string); 1022 } 1023 } 1024 1025 pragma(inline, true) 1026 void pushToStack(JSONValue val, ref JSONValue* current, ref JSONValue lastValue, string lastKey) 1027 { 1028 switch(current.type) with(JSONType) 1029 { 1030 case object: 1031 current.data.object[lastKey] = val; 1032 break; 1033 case array: 1034 current.data.array = JSONArray.append(current.data.array, val); 1035 break; 1036 case null_: 1037 *current = val; 1038 break; 1039 default: assert(false, "Unexpected stack type: "~current.getTypeName); 1040 } 1041 lastValue = val; 1042 } 1043 1044 1045 unittest 1046 { 1047 assert(parseJSON(` 1048 { 1049 "name": "redub", 1050 "description": "Dub Based Build System, with parallelization per packages and easier to contribute", 1051 "authors": ["Hipreme"], 1052 "targetPath": "build", 1053 "buildOptions": [ 1054 "debugInfo", 1055 "debugInfoC", 1056 "debugMode" 1057 ], 1058 "configurations": [ 1059 { 1060 "name": "cli", 1061 "targetType": "executable" 1062 }, 1063 { 1064 "name": "library", 1065 "targetType": "staticLibrary", 1066 "excludedSourceFiles": ["source/app.d"] 1067 } 1068 ], 1069 "license": "MIT", 1070 "dependencies": { 1071 "semver": {"path": "semver"}, 1072 "colorize": {"path": "colorize"}, 1073 "adv_diff": {"path": "adv_diff"}, 1074 "hipjson": {"path": "hipjson"}, 1075 "xxhash3": "~>0.0.5" 1076 } 1077 1078 }`).object["configurations"].array.length == 2); 1079 1080 } 1081 1082 unittest 1083 { 1084 enum json = ` 1085 { 1086 "D5F04185E96CC720": [ 1087 [ 1088 "First Value" 1089 ], 1090 [ 1091 "Second Value" 1092 ] 1093 ] 1094 }`; 1095 assert(parseJSON(json)["D5F04185E96CC720"].array[1].array[0].toString == `"Second Value"`); 1096 } 1097 1098 1099 pragma(inline, true) 1100 private char escapedCharacter(char a) 1101 { 1102 switch(a) 1103 { 1104 case 'n': return '\n'; 1105 case 't': return '\t'; 1106 case 'b': return '\b'; 1107 case 'r': return '\r'; 1108 default: return a; 1109 } 1110 }